#ifndef MDTX_USE_RCPPARMADILLO
#define MDTX_USE_RCPPARMADILLO
#endif

#include <RcppArmadillo.h>
using namespace Rcpp;

#include "mdtx-punch/include/TradesimPortfolio.h"
using namespace mdtx::punch;

using order_type = TpOrder;
using tradesim_portfolio_type = TradesimPortfolio<order_type>;
using XPtr_tp = XPtr<tradesim_portfolio_type>;
using id_type = typename tradesim_portfolio_type::id_type;
using quant_type = typename tradesim_portfolio_type::quant_type;

//' Create a Tradesim Portfolio object
//'
//' @param instr String vector Names of simulated instruments
//' @param init_share Numeric vector Initial holdings of corresponding instruments
//' @param t0 Logical vector T+0 flag of corresponding instruments
//' @param lot_quant Numeric vector Minimum tradable quantity of corresponding instruments
//' @param init_cash Numeric Initial portfolio cash
//' @return An external pointer to created Tradesim Portfolio
//' @export
// [[Rcpp::export]]
SEXP tp_create(StringVector instr, NumericVector init_share, LogicalVector t0, NumericVector lot_quant, double init_cash)
{
    std::vector<std::string> v_instr(instr.begin(), instr.end());
    std::vector<quant_type> v_init_share(init_share.begin(), init_share.end());
    std::vector<bool> v_t0(t0.begin(), t0.end());
    std::vector<quant_type> v_lot_quant(lot_quant.begin(), lot_quant.end());
    tradesim_portfolio_type *tp = new tradesim_portfolio_type(v_instr, v_init_share, v_t0, v_lot_quant, init_cash);
    return XPtr_tp(tp, true);
}

//' Forward market state of a Tradesim Portfolio object, update price ticks
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param tick Integer representation of Integer time (data.table ITime, IDate)
//' @param instr String vector Name of updated instruments
//' @param price Numeric vector Updated price
//' @param volume Numeric vector Updated volume
//' @param turnover Numeric vector Updated turnover
//' @param norder Numeric vector Updated number of orders
//' @param bid Numeric vector Updated BBO bid price
//' @param ask Numeric vector Updated BBO ask price
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_tick(SEXP tp, int tick, StringVector instr, NumericVector price, NumericVector volume, NumericVector turnover, NumericVector norder, NumericVector bid, NumericVector ask)
{
    XPtr_tp tpptr{tp};
    for (R_xlen_t i = 0; i < instr.size(); ++i)
    {
        tpptr->tick(tick, as<std::string>(instr[i]), price[i], volume[i], turnover[i], norder[i], bid[i], ask[i]);
    }
}

//' Forward market state of a Tradesim Portfolio object, update OHLC bars
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param tick Integer representation of Integer time (data.table ITime, IDate)
//' @param instr String vector Name of updated instruments
//' @param open Numeric vector Updated open
//' @param high Numeric vector Updated high
//' @param low Numeric vector Updated low
//' @param close Numeric vector Updated close
//' @param vwap Numeric vector Updated vwap
//' @param volume Numeric vector Updated volume
//' @param turnover Numeric vector Updated turnover
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_tick_bar(SEXP tp, int tick, StringVector instr, NumericVector open, NumericVector high, NumericVector low, NumericVector close, NumericVector vwap, NumericVector volume, NumericVector turnover)
{
    XPtr_tp tpptr{tp};
    for (R_xlen_t i = 0; i < instr.size(); ++i)
    {
        tpptr->tick(tick, as<std::string>(instr[i]), open[i], high[i], low[i], close[i], vwap[i], volume[i], turnover[i]);
    }
}

//' Sync market time of all instruments
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param tick Integer representation of Integer time (data.table ITime, IDate)
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_tick_sync(SEXP tp, int tick)
{
    XPtr_tp tpptr{tp};
    tpptr->tick_sync(tick);
}

//' Freeze market status so new orders won't match untill next tick
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_freeze(SEXP tp)
{
    XPtr_tp tpptr{tp};
    for (auto &exec : tpptr->executor)
    {
        exec.second.freeze_pool();
    }
}

//' Submit a buy order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param price Numeric Price
//' @param volume Numeric Volume
//' @param limit Logical Is the order a limit order? If false, a market order will be submitted.
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_buy(SEXP tp, String instr, double price, double volume, bool limit)
{
    XPtr_tp tpptr{tp};
    tpptr->buy(instr, price, volume, limit);
}

//' Submit a limit buy order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param price Numeric Price
//' @param volume Numeric Volume
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_buy_limit(SEXP tp, String instr, double price, double volume)
{
    tp_buy(tp, instr, price, volume, true);
}

//' Submit a market buy order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param volume Numeric Volume
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_buy_market(SEXP tp, String instr, double volume)
{
    tp_buy(tp, instr, 0, volume, false);
}

//' Submit a sell order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param price Numeric Price
//' @param volume Numeric Volume
//' @param limit Logical Is the order a limit order? If false, a market order will be submitted.
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_sell(SEXP tp, String instr, double price, double volume, bool limit)
{
    XPtr_tp tpptr{tp};
    tpptr->sell(instr, price, volume, limit);
}

//' Submit a limit sell order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param price Numeric Price
//' @param volume Numeric Volume
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_sell_limit(SEXP tp, String instr, double price, double volume)
{
    tp_sell(tp, instr, price, volume, true);
}

//' Submit a market sell order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param volume Numeric Volume
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_sell_market(SEXP tp, String instr, double volume)
{
    tp_sell(tp, instr, 0, volume, false);
}

//' Cancel an order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param seq_id Numeric Order id
//' @param volume Numeric Volume to cancel, 0 to cancel all
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_cancel(SEXP tp, String instr, R_xlen_t seq_id, double volume = 0)
{
    XPtr_tp tpptr{tp};
    tpptr->cancel(instr, seq_id, volume);
}

//' Cancel top-of-book buy order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param volume Numeric Volume to cancel, 0 to cancel all
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_cancel_bbo_buy(SEXP tp, String instr, double volume = 0)
{
    XPtr_tp tpptr{tp};
    tpptr->cancel_bbo_buy(instr, volume);
}

//' Cancel top-of-book sell order
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param volume Numeric Volume to cancel, 0 to cancel all
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_cancel_bbo_sell(SEXP tp, String instr, double volume = 0)
{
    XPtr_tp tpptr{tp};
    tpptr->cancel_bbo_sell(instr, volume);
}

//' Cancel all buy orders
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param volume Numeric Volume to cancel, 0 to cancel all
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_cancel_side_buy(SEXP tp, String instr, double volume = 0)
{
    XPtr_tp tpptr{tp};
    tpptr->cancel_side_buy(instr, volume);
}

//' Cancel all sell orders
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String Name of the instrument
//' @param volume Numeric Volume to cancel, 0 to cancel all
//' @return Nothing
//' @export
// [[Rcpp::export]]
void tp_cancel_side_sell(SEXP tp, String instr, double volume = 0)
{
    XPtr_tp tpptr{tp};
    tpptr->cancel_side_sell(instr, volume);
}

//' Get current positions
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_positions(SEXP tp)
{
    XPtr_tp tpptr{tp};
    R_xlen_t n = tpptr->num_instrument();
    StringVector instr(n);
    NumericVector share_total(n), share_avail(n), share_new(n), share_frozen(n), tnvr_buy(n), tnvr_sell(n), net_cash(n);

    auto it_instr = instr.begin();
    auto it_share_total = share_total.begin();
    auto it_share_avail = share_avail.begin();
    auto it_share_new = share_new.begin();
    auto it_share_frozen = share_frozen.begin();
    auto it_tnvr_buy = tnvr_buy.begin();
    auto it_tnvr_sell = tnvr_sell.begin();
    auto it_net_cash = net_cash.begin();

    for (const auto &exec : tpptr->executor)
    {
        const auto ins_id = exec.second.get_instrument_id();
        *it_instr++ = exec.first;
        *it_share_total++ = tpptr->share_total(ins_id);
        *it_share_avail++ = tpptr->share_avail(ins_id);
        *it_share_new++ = tpptr->share_new(ins_id);
        *it_share_frozen++ = tpptr->share_frozen(ins_id);
        *it_tnvr_buy++ = tpptr->tnvr_buy(ins_id);
        *it_tnvr_sell++ = tpptr->tnvr_sell(ins_id);
        *it_net_cash++ = tpptr->net_cash(ins_id);
    }

    return List::create(
        Named("instr") = instr,
        Named("share_total") = share_total,
        Named("share_avail") = share_avail,
        Named("share_new") = share_new,
        Named("share_frozen") = share_frozen,
        Named("tnvr_buy") = tnvr_buy,
        Named("tnvr_sell") = tnvr_sell,
        Named("net_cashflow") = net_cash);
}

//' Get current position or an instrument
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String name of the instrument
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_position(SEXP tp, String instr)
{
    XPtr_tp tpptr{tp};
    auto ins_id = tpptr->get_instrument_id(instr);
    double share_total, share_avail, share_new, share_frozen, tnvr_buy, tnvr_sell, net_cash;

    share_total = tpptr->share_total(ins_id);
    share_avail = tpptr->share_avail(ins_id);
    share_new = tpptr->share_new(ins_id);
    share_frozen = tpptr->share_frozen(ins_id);
    tnvr_buy = tpptr->tnvr_buy(ins_id);
    tnvr_sell = tpptr->tnvr_sell(ins_id);
    net_cash = tpptr->net_cash(ins_id);

    return List::create(
        Named("share_total") = share_total,
        Named("share_avail") = share_avail,
        Named("share_new") = share_new,
        Named("share_frozen") = share_frozen,
        Named("tnvr_buy") = tnvr_buy,
        Named("tnvr_sell") = tnvr_sell,
        Named("net_cashflow") = net_cash);
}

//' Get current position or an instrument
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String name of the instrument
//' @return List
//' @export
// [[Rcpp::export]]
LogicalVector tp_is_t0(SEXP tp, String instr)
{
    XPtr_tp tpptr{tp};
    return tpptr->is_t0(instr);
}

//' Get current portfolio cash details
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_cash(SEXP tp)
{
    XPtr_tp tpptr{tp};
    double net_cash, tnvr_buy, tnvr_sell, cash_total, cash_avail, cash_frozen, net_value, debt_ratio;

    net_cash = tpptr->net_cash();
    tnvr_buy = tpptr->tnvr_buy();
    tnvr_sell = tpptr->tnvr_sell();
    cash_total = tpptr->cash_total();
    cash_avail = tpptr->cash_avail();
    cash_frozen = tpptr->cash_frozen();
    net_value = tpptr->net_value();
    debt_ratio = tpptr->debt_ratio();

    return List::create(
        Named("net_value") = net_value,
        Named("debt_ratio") = debt_ratio,
        Named("cash_total") = cash_total,
        Named("cash_avail") = cash_avail,
        Named("cash_frozen") = cash_frozen,
        Named("tnvr_buy") = tnvr_buy,
        Named("tnvr_sell") = tnvr_sell,
        Named("net_cashflow") = net_cash);
}

//' Get all orders
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_orders(SEXP tp)
{
    XPtr_tp tpptr{tp};
    R_xlen_t n = tpptr->num_order();
    StringVector instr(n);
    NumericVector seq_id(n), tick_init(n), tick_last(n), price(n), quant(n);
    LogicalVector buy(n), limit(n), match(n), fill(n), cancel(n);

    auto it_instr = instr.begin();
    auto it_seq_id = seq_id.begin();
    auto it_tick_init = tick_init.begin();
    auto it_tick_last = tick_last.begin();
    auto it_price = price.begin();
    auto it_quant = quant.begin();
    auto it_buy = buy.begin();
    auto it_limit = limit.begin();
    auto it_match = match.begin();
    auto it_fill = fill.begin();
    auto it_cancel = cancel.begin();

    for (const auto &exec : tpptr->executor)
    {
        const auto ins_id = exec.second.get_instrument_id();
        for (const auto &order : tpptr->order_list(ins_id))
        {
            *it_instr++ = exec.first;
            *it_seq_id++ = order.seq_id;
            *it_tick_init++ = order.tick_init;
            *it_tick_last++ = order.tick_last;
            *it_price++ = order.price;
            *it_quant++ = order.quant;
            *it_buy++ = order.is_buy();
            *it_limit++ = order.is_limit();
            *it_match++ = order.is_match();
            *it_fill++ = order.is_fill();
            *it_cancel++ = order.is_cancel();
        }
    }

    return List::create(
        Named("instr") = instr,
        Named("seq_id") = seq_id,
        Named("tick_init") = tick_init,
        Named("tick_last") = tick_last,
        Named("price") = price,
        Named("volume") = quant,
        Named("buy") = buy,
        Named("limit") = limit,
        Named("match") = match,
        Named("fill") = fill,
        Named("cancel") = cancel);
}

//' Get orders of an instrument
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String name of the instrument
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_order(SEXP tp, String instr)
{
    XPtr_tp tpptr{tp};
    auto ins_id = tpptr->get_instrument_id(instr);
    R_xlen_t n = tpptr->order_list(ins_id).size();
    NumericVector seq_id(n), tick_init(n), tick_last(n), price(n), quant(n);
    LogicalVector buy(n), limit(n), match(n), fill(n), cancel(n);

    auto it_seq_id = seq_id.begin();
    auto it_tick_init = tick_init.begin();
    auto it_tick_last = tick_last.begin();
    auto it_price = price.begin();
    auto it_quant = quant.begin();
    auto it_buy = buy.begin();
    auto it_limit = limit.begin();
    auto it_match = match.begin();
    auto it_fill = fill.begin();
    auto it_cancel = cancel.begin();

    for (const auto &order : tpptr->order_list(ins_id))
    {
        *it_seq_id++ = order.seq_id;
        *it_tick_init++ = order.tick_init;
        *it_tick_last++ = order.tick_last;
        *it_price++ = order.price;
        *it_quant++ = order.quant;
        *it_buy++ = order.is_buy();
        *it_limit++ = order.is_limit();
        *it_match++ = order.is_match();
        *it_fill++ = order.is_fill();
        *it_cancel++ = order.is_cancel();
    }

    return List::create(
        Named("seq_id") = seq_id,
        Named("tick_init") = tick_init,
        Named("tick_last") = tick_last,
        Named("price") = price,
        Named("volume") = quant,
        Named("buy") = buy,
        Named("limit") = limit,
        Named("match") = match,
        Named("fill") = fill,
        Named("cancel") = cancel);
}

//' Get all trade records
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_trades(SEXP tp)
{
    XPtr_tp tpptr{tp};
    R_xlen_t n = tpptr->num_trade();
    StringVector instr(n);
    NumericVector seq_id(n), tick(n), price(n), volume(n), turnover(n), exposure(n);
    LogicalVector buy(n), limit(n);

    auto it_instr = instr.begin();
    auto it_seq_id = seq_id.begin();
    auto it_tick = tick.begin();
    auto it_price = price.begin();
    auto it_volume = volume.begin();
    auto it_turnover = turnover.begin();
    auto it_exposure = exposure.begin();
    auto it_buy = buy.begin();
    auto it_limit = limit.begin();

    for (const auto &exec : tpptr->executor)
    {
        const auto ins_id = exec.second.get_instrument_id();
        for (const auto &record : tpptr->record_list(ins_id))
        {
            *it_instr++ = exec.first;
            *it_seq_id++ = record.seq_id;
            *it_tick++ = record.tick;
            *it_price++ = record.price;
            *it_volume++ = record.volume;
            *it_turnover++ = record.turnover;
            *it_exposure++ = record.exposure;
            *it_buy++ = record.buy;
            *it_limit++ = record.limit;
        }
    }

    return List::create(
        Named("instr") = instr,
        Named("seq_id") = seq_id,
        Named("tick") = tick,
        Named("price") = price,
        Named("volume") = volume,
        Named("turnover") = turnover,
        Named("exposure") = exposure,
        Named("buy") = buy,
        Named("limit") = limit);
}

//' Get trade records of an instrument
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @param instr String name of the instrument
//' @return List
//' @export
// [[Rcpp::export]]
List tp_get_trade(SEXP tp, String instr)
{
    XPtr_tp tpptr{tp};
    auto ins_id = tpptr->get_instrument_id(instr);
    R_xlen_t n = tpptr->record_list(ins_id).size();
    NumericVector seq_id(n), tick(n), price(n), volume(n), turnover(n), exposure(n);
    LogicalVector buy(n), limit(n);

    auto it_seq_id = seq_id.begin();
    auto it_tick = tick.begin();
    auto it_price = price.begin();
    auto it_volume = volume.begin();
    auto it_turnover = turnover.begin();
    auto it_exposure = exposure.begin();
    auto it_buy = buy.begin();
    auto it_limit = limit.begin();

    for (const auto &record : tpptr->record_list(ins_id))
    {
        *it_seq_id++ = record.seq_id;
        *it_tick++ = record.tick;
        *it_price++ = record.price;
        *it_volume++ = record.volume;
        *it_turnover++ = record.turnover;
        *it_exposure++ = record.exposure;
        *it_buy++ = record.buy;
        *it_limit++ = record.limit;
    }

    return List::create(
        Named("seq_id") = seq_id,
        Named("tick") = tick,
        Named("price") = price,
        Named("volume") = volume,
        Named("turnover") = turnover,
        Named("exposure") = exposure,
        Named("buy") = buy,
        Named("limit") = limit);
}

//' Estimate current portfolio net value
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return Numeric
//' @export
// [[Rcpp::export]]
double tp_get_netvalue(SEXP tp)
{
    XPtr_tp tpptr{tp};
    return tpptr->net_value();
}

//' Estimate initial (upon first tick) portfolio net value
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return Numeric
//' @export
// [[Rcpp::export]]
double tp_get_netvalue0(SEXP tp)
{
    XPtr_tp tpptr{tp};
    return tpptr->net_value0();
}

//' Estimate current portfolio debt to net value ratio
//'
//' @param tp External pointer to the Tradesim Portfolio object
//' @return Numeric
//' @export
// [[Rcpp::export]]
double tp_get_debtratio(SEXP tp)
{
    XPtr_tp tpptr{tp};
    return tpptr->debt_ratio();
}
